﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ModbusTCPLib
{
    public class ModbusTCP
    {
        #region 属性与字段
        /// <summary>
        /// 发送超时时间
        /// </summary>
        public int SendTimeOut { get; set; } = 2000;

        /// <summary>
        /// 接收超时时间
        /// </summary>
        public int ReceiveTimeOut { get; set; } = 2000;

        //创建一个Socket对象
        private Socket socket;

        /// <summary>
        /// 锁对象
        /// </summary>
        private SimpleHybirdLock hybirdLock = new SimpleHybirdLock();

        /// <summary>
        /// 每次接收前延时的时间
        /// </summary>
        public int SleepTime { get; set; } = 1;

        /// <summary>
        /// 最大的等待次数
        /// </summary>
        public int MaxWaitTimes { get; set; } = 10;

        /// <summary>
        /// 单元标识符
        /// </summary>
        public byte SlaveId { get; set; } = 0x01;
        #endregion

        #region 建立连接与断开连接
        /// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口号</param>
        /// <returns>返回结果</returns>
        public bool Connect(string ip,int port)
        {
            // Socket实例化
            this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.socket.SendTimeout = SendTimeOut;
            this.socket.ReceiveTimeout = ReceiveTimeOut;
            try
            {
                if (IPAddress.TryParse(ip, out IPAddress ipAddress))
                {
                    this.socket.Connect(ip, port);
                }
                else
                {
                    this.socket.Connect(ip, port);
                }
            }
            catch(Exception)
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            if(this.socket != null)
            {
                this.socket.Close();
            }
        }
        #endregion

        #region 01H读取输出线圈
        /// <summary>
        /// 01H读取输出线圈
        /// </summary>
        /// <param name="start">起始线圈地址</param>
        /// <param name="length">线圈长度</param>
        /// <returns>返回数据</returns>
        public byte[] ReadOutputCoils(ushort start, ushort length)
        {
            //第一步：拼接报文

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            //事务标识+协议标识+长度+单元标识+功能码+线圈地址+长度

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识+功能码
            SendCommand.Add(0x00, 0x06, SlaveId, 0x01);

            //线圈地址+长度
            SendCommand.Add(start);
            SendCommand.Add(length);

            byte[] receive = null;

            int byteLength = length % 8 == 0 ? length / 8 : length / 8 + 1;
            //第二步 第三步： 发送并接收报文
            if(SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                if(receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x01 && receive[8] == byteLength)
                    {
                        //第五步：解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 9, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion

        #region 02H读取输入线圈
        /// <summary>
        /// 02H读取输入线圈
        /// </summary>
        /// <param name="start">起始线圈地址</param>
        /// <param name="length">线圈长度</param>
        /// <returns>返回数据</returns>
        public byte[] ReadInputCoils(ushort start, ushort length)
        {
            //第一步：拼接报文

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            //事务标识+协议标识+长度+单元标识+功能码+线圈地址+长度

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识+功能码
            SendCommand.Add(0x00, 0x06, SlaveId, 0x02);

            //线圈地址+长度
            SendCommand.Add(start);
            SendCommand.Add(length);

            byte[] receive = null;

            int byteLength = length % 8 == 0 ? length/8 : length/8 + 1;
            //第二步 第三步： 发送并接收报文
            if (SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                if (receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x02 && receive[8] == byteLength)
                    {
                        //第五步：解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 9, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion

        #region 03H读取输出寄存器
        /// <summary>
        /// 读取输出寄存器
        /// </summary>
        /// <param name="start">起始寄存器地址</param>
        /// <param name="length">寄存器长度</param>
        /// <returns>返回字节数组</returns>
        public byte[] ReadOutputRegisters(ushort start, ushort length)
        {
            //第一步：拼接报文

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            //事务标识+协议标识+长度+单元标识+功能码+起始寄存器地址+寄存器长度

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识+功能码
            SendCommand.Add(0x00, 0x06, SlaveId, 0x03);

            //寄存器地址+寄存器长度
            SendCommand.Add(start);
            SendCommand.Add(length);

            byte[] receive = null;

            int byteLength = length * 2;
            //第二步 第三步： 发送并接收报文
            if (SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                if (receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x03 && receive[8] == byteLength)
                    {
                        //第五步：解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 9, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion

        #region 04H读取输入寄存器
        /// <summary>
        /// 读取输出寄存器
        /// </summary>
        /// <param name="start">起始寄存器地址</param>
        /// <param name="length">寄存器长度</param>
        /// <returns>返回字节数组</returns>
        public byte[] ReadInputRegisters(ushort start, ushort length)
        {
            //第一步：拼接报文

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            //事务标识+协议标识+长度+单元标识+功能码+起始寄存器地址+寄存器长度

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识+功能码
            SendCommand.Add(0x00, 0x06, SlaveId, 0x04);

            //寄存器地址+寄存器长度
            SendCommand.Add(start);
            SendCommand.Add(length);

            byte[] receive = null;

            int byteLength = length * 2;
            //第二步 第三步： 发送并接收报文
            if (SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                if (receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x04 && receive[8] == byteLength)
                    {
                        //第五步：解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 9, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion

        #region 05H预置单线圈
        /// <summary>
        /// 预置单线圈
        /// </summary>
        /// <param name="start">线圈地址</param>
        /// <param name="value">线圈值</param>
        /// <returns>返回结果</returns>
        public bool PreSetSingleCoil(ushort start, bool value)
        {
            //第一步：拼接报文

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            //事务标识+协议标识+长度+单元标识+功能码+线圈地址+线圈值(0xFF 0x00 / 0x00 0x00)

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识+功能码
            SendCommand.Add(0x00, 0x06, SlaveId, 0x05);

            //线圈地址
            SendCommand.Add(start);

            //线圈值
            SendCommand.Add(value ? (byte)0xFF : (byte)0x00, 0x00);

            byte[] receive = null;


            //第二步 第三步： 发送并接收报文
            if (SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                if (receive.Length == 12)
                {
                    return ByteArrayEquals(SendCommand.Array, receive);
                }
            }
            return false;
        }
        #endregion

        #region 06H预置单寄存器
        /// <summary>
        /// 预置单线圈
        /// </summary>
        /// <param name="start">寄存器地址</param>
        /// <param name="value">寄存器值</param>
        /// <returns>返回结果</returns>
        public bool PreSetSingleRegister(ushort start, byte[] value)
        {
            //第一步：拼接报文

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            //事务标识+协议标识+长度+单元标识+功能码+ 寄存器地址+寄存器值

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识+功能码
            SendCommand.Add(0x00, 0x06, SlaveId, 0x06);

            //寄存器地址
            SendCommand.Add(start);

            //寄存器值
            SendCommand.Add(value);

            byte[] receive = null;

            //第二步：发送报文
            //第三步：接收报文
            if (SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                if (receive.Length == 12)
                {
                    return ByteArrayEquals(SendCommand.Array, receive);
                }
            }
            return false;
        }
        /// <summary>
        /// 预置单寄存器
        /// </summary>
        /// <param name="start">开始寄存器地址</param>
        /// <param name="value">short类型</param>
        /// <returns>返回结果</returns>
        public bool PreSetSingleRegister( ushort start, short value)
        {
            return PreSetSingleRegister(start, BitConverter.GetBytes(value).Reverse().ToArray());
        }
        /// <summary>
        /// 预置单寄存器
        /// </summary>
        /// <param name="start">开始寄存器地址</param>
        /// <param name="value">ushort类型</param>
        /// <returns>返回结果</returns>
        public bool PreSetSingleRegister( ushort start, ushort value)
        {
            return PreSetSingleRegister( start, BitConverter.GetBytes(value));
        }
        #endregion

        #region 0FH预置多线圈
        /// <summary>
        /// 预置多线圈
        /// </summary>
        /// <param name="start">起始线圈地址</param>
        /// <param name="value">写入值</param>
        /// <returns>返回结果</returns>
        public bool PreSetMultiCoils(ushort start, bool[] value)
        {
            //第一步：拼接报文

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            byte[] setArray = GetByteArrayFromBoolArray(value);

            //事务标识+协议标识+长度+单元标识+功能码+ 起始线圈地址+线圈数量+字节计数+字节数据

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度
            SendCommand.Add((short)(7+ setArray.Length));

            //单元标识+功能码
            SendCommand.Add(SlaveId, 0x0F);

            //起始线圈地址
            SendCommand.Add(start);

            //线圈数量
            SendCommand.Add((short)value.Length);

            //字节计数
            SendCommand.Add((byte)setArray.Length);

            //字节数据
            SendCommand.Add(setArray);

            byte[] receive = null;

            //第二步：发送报文
            //第三步：接收报文
            if (SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                byte[] send = new byte[12];

                Array.Copy(SendCommand.Array, 0, send, 0, 12);

                send[4] = 0x00;
                send[5] = 0x06;

                return ByteArrayEquals(send, receive);
            }
            return false;
        }
        #endregion

        #region 10H预置多寄存器
        /// <summary>
        /// 预置多线圈
        /// </summary>
        /// <param name="start">起始寄存器地址</param>
        /// <param name="value">写入值</param>
        /// <returns>返回结果</returns>
        public bool PreSetMultiRegisters(ushort start, byte[] values)
        {
            //第一步：拼接报文

            if(values == null || values.Length == 0 || values.Length % 2 == 1)
            {
                return false;
            }

            //创建一个ByteArray对象
            ByteArray SendCommand = new ByteArray();

            //事务标识+协议标识+长度+单元标识+功能码+ 起始寄存器地址+寄存器数量+字节计数+字节数据

            //事务标识 + 协议标识
            SendCommand.Add(0x00, 0x00, 0x00, 0x00);

            //长度
            SendCommand.Add((short)(7 + values.Length));

            //单元标识+功能码
            SendCommand.Add(SlaveId, 0x10);

            //起始线圈地址
            SendCommand.Add(start);

            //寄存器数量
            SendCommand.Add((short)(values.Length / 2));

            //字节计数
            SendCommand.Add((byte)values.Length);

            //字节数据
            SendCommand.Add(values);

            byte[] receive = null;

            //第二步：发送报文
            //第三步：接收报文
            if (SendAndReceive(SendCommand.Array, ref receive))
            {
                //第四步：验证报文
                byte[] send = new byte[12];

                Array.Copy(SendCommand.Array, 0, send, 0, 12);

                send[4] = 0x00;
                send[5] = 0x06;

                return ByteArrayEquals(send, receive);
            }
            return false;
        }
        #endregion

        #region 通用发送并接收方法
        /// <summary>
        /// 发送并接收方法
        /// </summary>
        /// <param name="send">发送报文</param>
        /// <param name="receive">接收报文</param>
        /// <returns>返回结果</returns>
        private bool SendAndReceive(byte[] send, ref byte[] receive)
        {
            //加锁
            hybirdLock.Enter();

            //定义一个Buffer
            byte[] buffer = new byte[1024];

            //定义一个内存(因为有可能接收不完整)
            MemoryStream stream = new MemoryStream();

            try
            {
                ///发送报文
                socket.Send(send, send.Length, SocketFlags.None);

                int timer = 0;

                while (true)
                {
                    Thread.Sleep(SleepTime);
                    //判断缓冲区有没有数据
                    if (socket.Available > 0)
                    {
                        //接收数据并放到Buffer
                        int count = socket.Receive(buffer, SocketFlags.None);

                        //将读取的数据放到Stream中
                        stream.Write(buffer, 0, count);
                    }
                    else
                    {
                        timer++;
                        //先判断Stram中有没有数据
                        if (stream.Length > 0)
                        {
                            break;
                        }
                        else if (timer > MaxWaitTimes)  //超时读取
                        {
                            return false;
                        }
                    }
                }

                receive = stream.ToArray();
                return true;
            }
            catch(Exception)
            {
                return false;
            }
            finally
            {
                //解锁
                hybirdLock.Leave();
            }
        }
        #endregion

        #region 数组比较方法
        /// <summary>
        /// 数组比较方法 0x01 0x02 01-02
        /// </summary>
        /// <param name="b1"></param>
        /// <param name="b2"></param>
        /// <returns></returns>
        private bool ByteArrayEquals(byte[] b1, byte[] b2)
        {
            return BitConverter.ToString(b1) == BitConverter.ToString(b2);
        }
        #endregion

        #region 将布尔数组转换成字节数组
        private byte[] GetByteArrayFromBoolArray(bool[] value)
        {
            int byteLength = value.Length % 8 == 0 ? value.Length / 8 : value.Length / 8 + 1;
            byte[] result = new byte[byteLength];
            for (int i = 0; i < result.Length; i++)
            {
                //获取每个字节的值
                int total = value.Length < 8 * (i + 1) ? value.Length - 8 * i : 8;
                for (int j = 0; j < total; j++)
                {
                    result[i] = SetBitValue(result[i], j, value[8 * i + j]);
                }
            }
            return result;
        }
        /// <summary>
        /// 将某个字节某个位置位或复位
        /// </summary>
        /// <param name="src"></param>
        /// <param name="bit"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private byte SetBitValue(byte src, int bit, bool value)
        {
            return value ? (byte)(src | (byte)Math.Pow(2, bit)) : (byte)(src & ~(byte)Math.Pow(2, bit));
        }
        #endregion
    }

    #region 简单的混合锁
    /// <summary>
    ///  一个简单的混合线程同步锁，采用了基元用户加基元内核同步构造实现
    /// </summary>
    public sealed class SimpleHybirdLock : IDisposable
    {
        #region IDisposable Support
        private bool disposedValue = false; //要检查恻冗余调用
        void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    //TODO:释放托管状态(托管对象)
                }
                //TODO:释放未托管的资源(未托管的对象)并在以下内容中代替终结器
                //TODO:将大型字段设置为null
                m_waiterLock.Close();
            }
        }
        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }
        /// <summary>
        /// 基于用户模式构造同步锁
        /// </summary>
        private Int32 m_waiters = 0;

        /// <summary>
        /// 基于内核模式构造同步锁
        /// </summary>
        private AutoResetEvent m_waiterLock = new AutoResetEvent(false);
        /// <summary>
        /// 获取锁
        /// </summary>
        public void Enter()
        {
            if (Interlocked.Increment(ref m_waiters) == 1) return;
            m_waiterLock.WaitOne();
        }
        /// <summary>
        /// 离开锁
        /// </summary>
        public void Leave()
        {
            if (Interlocked.Decrement(ref m_waiters) == 0) return;
            m_waiterLock.Set();
        }
        /// <summary>
        /// 获取当前锁是否等待当中
        /// </summary>
        public bool IsWaitting => m_waiters == 0;
        #endregion
    }
    #endregion
}
